tests/test-gpg-signed-commit: Test more key states
authorDan Nicholson <nicholson@endlessm.com>
Mon, 17 Jun 2019 20:25:22 +0000 (15:25 -0500)
committerAtomic Bot <atomic-devel@projectatomic.io>
Wed, 19 Jun 2019 17:17:11 +0000 (17:17 +0000)
Extend test-gpg-signed-commit.sh to test various key states. If gpg is
found that supports the required options, keys will be generated on the
fly and changed in various ways to exercise the output from
`ostree_gpg_verify_result_describe_variant` used in `ostree show`.

I tested this using gnupg 2.2.12, so I hope it works well enough on
various gpgs found in the wild.

Closes: #1872
Approved by: cgwalters

tests/libtest.sh
tests/test-gpg-signed-commit.sh

index 5f381c671300777dccd82b8cb1a6b9ddbcb5e16c..99d7c9672791e2c93c96fa5b562b0248ce8cde0a 100755 (executable)
@@ -600,6 +600,39 @@ has_gpgme () {
     true
 }
 
+# Find an appropriate gpg program to use. We want one that has the
+# --generate-key, --quick-set-expire and --quick-add-key options. The
+# gpg program to use is returend.
+which_gpg () {
+    local gpg
+    local gpg_options
+    local needed_options=(
+        --generate-key
+        --quick-set-expire
+        --quick-add-key
+    )
+    local opt
+
+    # Prefer gpg2 in case gpg refers to gpg1
+    if which gpg2 &>/dev/null; then
+        gpg=gpg2
+    elif which gpg &>/dev/null; then
+        gpg=gpg
+    else
+        # Succeed but don't return anything.
+        return 0
+    fi
+
+    # Make sure all the needed options are available
+    gpg_options=$(${gpg} --dump-options) || return 0
+    for opt in ${needed_options[*]}; do
+      grep -q -x -e "${opt}" <<< "${gpg_options}" || return 0
+    done
+
+    # Found an appropriate gpg
+    echo ${gpg}
+}
+
 libtest_cleanup_gpg () {
     local gpg_homedir=${1:-${test_tmpdir}/gpghome}
     gpg-connect-agent --homedir "${gpg_homedir}" killagent /bye || true
index 992a2ba1db48cee73951067500c41089ebbe17fe..7bd5950291619abe0c5f2f64f4980c952dfe6489 100755 (executable)
@@ -29,7 +29,16 @@ if ! has_gpgme; then
     exit 0
 fi
 
-echo "1..1"
+num_tests=1
+
+# Run some more tests if an appropriate GPG is available
+num_gpg_tests=8
+GPG=$(which_gpg)
+if [ -n "${GPG}" ]; then
+  let num_tests+=num_gpg_tests
+fi
+
+echo "1..${num_tests}"
 
 setup_test_repository "archive"
 
@@ -83,3 +92,216 @@ fi
 libtest_cleanup_gpg
 
 echo "ok"
+
+# Remaining tests require gpg
+if [ -z "${GPG}" ]; then
+  exit 0
+fi
+
+# Create a temporary GPG homedir
+tmpgpg_home=${test_tmpdir}/tmpgpghome
+mkdir -m700 ${tmpgpg_home}
+
+# Create an temporary trusted GPG directory
+tmpgpg_trusted=${test_tmpdir}/tmpgpgtrusted
+tmpgpg_trusted_keyring=${tmpgpg_trusted}/keyring.gpg
+export OSTREE_GPG_HOME=${tmpgpg_trusted}
+mkdir -p ${tmpgpg_trusted}
+
+# Create one normal signing key and one signing key with a subkey. See
+# https://www.gnupg.org/documentation/manuals/gnupg/Unattended-GPG-key-generation.html.
+${GPG} --homedir=${tmpgpg_home} --batch --generate-key <<"EOF"
+Key-Type: RSA
+Key-Length: 2048
+Key-Usage: sign
+Name-Real: Test Key 1
+Expire-Date: 0
+%no-protection
+%transient-key
+%commit
+Key-Type: RSA
+Key-Length: 2048
+Key-Usage: sign
+Subkey-Type: RSA
+Subkey-Length: 2048
+Subkey-Usage: sign
+Name-Real: Test Key 2
+Expire-Date: 0
+%no-protection
+%transient-key
+%commit
+EOF
+
+# Figure out the key IDs and fingerprints. Assume that the order of the
+# keys matches those specified in the generation.
+#
+# https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob_plain;f=doc/DETAILS
+key1_id=
+key1_fpr=
+key2_id=
+key2_fpr=
+key2_sub_id=
+key2_sub_fpr=
+gpg_seckey_listing=$(${GPG} --homedir=${tmpgpg_home} --list-secret-keys --with-colons)
+while IFS=: read -a fields; do
+  if [ "${fields[0]}" = sec ]; then
+    # Secret key - key ID is in field 5
+    if [ -z "${key1_id}" ]; then
+      key1_id=${fields[4]}
+    else
+      key2_id=${fields[4]}
+    fi
+  elif [ "${fields[0]}" = ssb ]; then
+    # Secret subkey - key ID is in field 5
+    key2_sub_id=${fields[4]}
+  elif [ "${fields[0]}" = fpr ]; then
+    # Fingerprint record - the fingerprint ID is in field 10
+    if [ -z "${key1_fpr}" ]; then
+      key1_fpr=${fields[9]}
+    elif [ -z "${key2_fpr}" ]; then
+      key2_fpr=${fields[9]}
+    else
+      key2_sub_fpr=${fields[9]}
+    fi
+  fi
+done <<< "${gpg_seckey_listing}"
+
+# Create a commit and sign it with both key1 and key2_sub
+${OSTREE} commit -b test2 -s "A GPG signed commit" -m "Signed commit body" \
+  --tree=dir=files --gpg-homedir=${tmpgpg_home} \
+  --gpg-sign=${key1_id} --gpg-sign=${key2_sub_id}
+${OSTREE} show test2 > test2-show
+assert_file_has_content test2-show '^Found 2 signatures'
+assert_file_has_content test2-show 'public key not found'
+assert_file_has_content test2-show "${key1_id}"
+assert_file_has_content test2-show "${key2_sub_id}"
+
+echo "ok signed with both generated keys"
+
+# Export the public keys and check again
+${GPG} --homedir=${tmpgpg_home} --export ${key1_id} ${key2_id} > ${tmpgpg_trusted_keyring}
+${OSTREE} show test2 > test2-show
+assert_file_has_content test2-show '^Found 2 signatures'
+assert_not_file_has_content test2-show 'public key not found'
+assert_file_has_content test2-show "${key1_id}"
+assert_file_has_content test2-show "${key2_sub_id}"
+assert_file_has_content test2-show 'Good signature from "Test Key 1 <>"'
+assert_file_has_content test2-show 'Good signature from "Test Key 2 <>"'
+assert_file_has_content test2-show "Primary key ID ${key2_id}"
+
+echo "ok verified both generated keys"
+
+# Make key1 expired, wait until it's expired, export the public keys and check
+# again
+${GPG} --homedir=${tmpgpg_home} --quick-set-expire ${key1_fpr} seconds=1
+sleep 2
+${GPG} --homedir=${tmpgpg_home} --export ${key1_id} ${key2_id} > ${tmpgpg_trusted_keyring}
+${OSTREE} show test2 > test2-show
+assert_file_has_content test2-show '^Found 2 signatures'
+assert_not_file_has_content test2-show 'public key not found'
+assert_file_has_content test2-show "${key1_id}"
+assert_file_has_content test2-show "${key2_sub_id}"
+assert_file_has_content test2-show 'BAD signature from "Test Key 1 <>"'
+assert_file_has_content test2-show 'Key expired'
+assert_file_has_content test2-show 'Good signature from "Test Key 2 <>"'
+assert_file_has_content test2-show "Primary key ID ${key2_id}"
+
+echo "ok verified with key1 expired"
+
+# Unexpire key1, expire key2 primary, wait until it's expired, export the
+# public keys and check again
+${GPG} --homedir=${tmpgpg_home} --quick-set-expire ${key1_fpr} seconds=0
+${GPG} --homedir=${tmpgpg_home} --quick-set-expire ${key2_fpr} seconds=1
+sleep 2
+${GPG} --homedir=${tmpgpg_home} --export ${key1_id} ${key2_id} > ${tmpgpg_trusted_keyring}
+${OSTREE} show test2 > test2-show
+assert_file_has_content test2-show '^Found 2 signatures'
+assert_not_file_has_content test2-show 'public key not found'
+assert_file_has_content test2-show "${key1_id}"
+assert_file_has_content test2-show "${key2_sub_id}"
+assert_file_has_content test2-show 'Good signature from "Test Key 1 <>"'
+assert_file_has_content test2-show 'BAD signature from "Test Key 2 <>"'
+assert_not_file_has_content test2-show 'Key expired'
+assert_file_has_content test2-show "Primary key ID ${key2_id}"
+assert_file_has_content test2-show 'Primary key expired'
+
+echo "ok verified with key2 primary expired"
+
+# Expire key2 subkey, wait until it's expired, export the public keys and
+# check again
+${GPG} --homedir=${tmpgpg_home} --quick-set-expire ${key2_fpr} seconds=1 ${key2_sub_fpr}
+sleep 2
+${GPG} --homedir=${tmpgpg_home} --export ${key1_id} ${key2_id} > ${tmpgpg_trusted_keyring}
+${OSTREE} show test2 > test2-show
+assert_file_has_content test2-show '^Found 2 signatures'
+assert_not_file_has_content test2-show 'public key not found'
+assert_file_has_content test2-show "${key1_id}"
+assert_file_has_content test2-show "${key2_sub_id}"
+assert_file_has_content test2-show 'Good signature from "Test Key 1 <>"'
+assert_file_has_content test2-show 'BAD signature from "Test Key 2 <>"'
+assert_file_has_content test2-show 'Key expired'
+assert_file_has_content test2-show "Primary key ID ${key2_id}"
+assert_file_has_content test2-show 'Primary key expired'
+
+echo "ok verified with key2 primary and subkey expired"
+
+# Unexpire key2 primary, export the public keys and check again
+${GPG} --homedir=${tmpgpg_home} --quick-set-expire ${key2_fpr} seconds=0
+${GPG} --homedir=${tmpgpg_home} --export ${key1_id} ${key2_id} > ${tmpgpg_trusted_keyring}
+${OSTREE} show test2 > test2-show
+assert_file_has_content test2-show '^Found 2 signatures'
+assert_not_file_has_content test2-show 'public key not found'
+assert_file_has_content test2-show "${key1_id}"
+assert_file_has_content test2-show "${key2_sub_id}"
+assert_file_has_content test2-show 'Good signature from "Test Key 1 <>"'
+assert_file_has_content test2-show 'BAD signature from "Test Key 2 <>"'
+assert_file_has_content test2-show 'Key expired'
+assert_file_has_content test2-show "Primary key ID ${key2_id}"
+assert_not_file_has_content test2-show 'Primary key expired'
+
+echo "ok verified with key2 subkey expired"
+
+# Add a second subkey but don't export it to the trusted keyring so that a new
+# commit signed with fails.
+${GPG} --homedir=${tmpgpg_home} --batch --passphrase '' \
+  --quick-add-key ${key2_fpr} rsa2048 sign never
+gpg_seckey_listing=$(${GPG} --homedir=${tmpgpg_home} --list-secret-keys --with-colons)
+key2_sub2_id=$(awk -F: '{if ($1 == "ssb") print $5}' <<< "${gpg_seckey_listing}" | tail -n1)
+key2_sub2_fpr=$(awk -F: '{if ($1 == "fpr") print $10}' <<< "${gpg_seckey_listing}" | tail -n1)
+${OSTREE} commit -b test2 -s "A GPG signed commit" -m "Signed commit body" \
+  --tree=dir=files --gpg-homedir=${tmpgpg_home} \
+  --gpg-sign=${key1_id} --gpg-sign=${key2_sub2_id}
+${OSTREE} show test2 > test2-show
+assert_file_has_content test2-show '^Found 2 signatures'
+assert_file_has_content test2-show "${key1_id}"
+assert_file_has_content test2-show "${key2_sub2_id}"
+assert_file_has_content test2-show 'Good signature from "Test Key 1 <>"'
+assert_file_has_content test2-show 'public key not found'
+
+echo "ok verified with key2 sub2 missing"
+
+# Revoke key1 by importing the revocation certificate created when generating
+# the key. The cert should be in $homedir/openpgp-revocs.d/$fpr.rev with
+# recent gnupg. However, it tries to prevent you from accidentally revoking
+# things by adding a : before the block, so it needs to be stripped.
+key1_rev=${tmpgpg_home}/openpgp-revocs.d/${key1_fpr}.rev
+if [ -f ${key1_rev} ]; then
+  sed -i '/BEGIN PGP PUBLIC KEY BLOCK/s/^://' ${key1_rev}
+  ${GPG} --homedir=${tmpgpg_home} --import ${key1_rev}
+  # Export both keys again
+  ${GPG} --homedir=${tmpgpg_home} --export ${key1_id} ${key2_id} > ${tmpgpg_trusted_keyring}
+  ${OSTREE} show test2 > test2-show
+  assert_file_has_content test2-show '^Found 2 signatures'
+  assert_not_file_has_content test2-show 'public key not found'
+  assert_file_has_content test2-show "${key1_id}"
+  assert_file_has_content test2-show "${key2_sub2_id}"
+  assert_file_has_content test2-show 'Key revoked'
+  assert_file_has_content test2-show 'Good signature from "Test Key 2 <>"'
+  assert_file_has_content test2-show "Primary key ID ${key2_id}"
+
+  echo "ok verified with key1 revoked"
+else
+  echo "ok # SKIP could not find key revocation certificate"
+fi
+
+libtest_cleanup_gpg ${tmpgpg_home}